﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.IO;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using ArgusPCLib.Collections;
using ArgusPCLib.Reflection;

namespace ArgusPCLib.Xml
{
	public class Deserializer : IDisposable
	{
		XmlReader xmlReader;
		ListPair<string, Reference<object>> path;

		public string RootName { get; private set; }

		public Deserializer(XmlReader xmlReader)
		{
			this.xmlReader = xmlReader;
			this.xmlReader.MoveToNode(XmlNodeType.Element, false);
			this.RootName = this.xmlReader.LocalName;
			this.xmlReader.MoveToNode(XmlNodeType.Element, true);
			this.path = new ListPair<string, Reference<object>>();
		}

		public static Deserializer FromStream(Stream stream)
		{
			XmlReaderSettings settings = new XmlReaderSettings();
			return Deserializer.FromStream(stream, settings);
		}

		public static Deserializer FromStream(Stream stream, XmlReaderSettings settings)
		{
			XmlReader xmlReader = XmlReader.Create(stream, settings);
			Deserializer deserializer = new Deserializer(xmlReader);
			return deserializer;
		}

		public ListPair<string, object> DeserializeAll()
		{
			if (this.xmlReader.MoveToNode(XmlNodeType.Element, false) == false)
				return null;
			int depth = this.xmlReader.Depth;
			ListPair<string, object> RetVal = new ListPair<string, object>();
			string name;
			object obj = this.Deserialize(depth, out name);
			while (obj != null)
			{
				RetVal.Add(name, obj);
				obj = this.Deserialize(depth, out name);
			}
			return RetVal;
		}

		public object Deserialize()
		{
			string name;
			return this.Deserialize(out name);
		}

		public object Deserialize(out string name)
		{
			return this.Deserialize(-1, out name);
		}

		private object Deserialize(int depth, out string name)
		{
			if (this.xmlReader.MoveToNode(XmlNodeType.Element, false) == false || (depth > -1 && this.xmlReader.Depth != depth))
			{
				name = null;
				return null;
			}
			string typeName = this.xmlReader.GetAttribute(Serializer.NameType);
			Type type = Type.GetType(typeName);
			name = XmlConvert.DecodeName(this.xmlReader.LocalName);
			return this.Deserialize(type);
		}

		private object Deserialize(Type type)
		{
			object objRef = this.ReadAsReference();
			if (objRef != null)
			{
				this.xmlReader.MoveToNode(XmlNodeType.EndElement, false);
				return objRef;
			}

			if (this.xmlReader.IsEmptyElement == true)
			{
				this.xmlReader.Read();
				return TypeInitializer.CreateInstance(type);
			}

			Reference<object> obj = new Reference<object>(null);
			this.path.Add(this.xmlReader.LocalName, obj);

			if (type.Is(typeof(ISerializable), false, false))
			{
				int depth = this.xmlReader.Depth;
				this.xmlReader.Read();
				ISerializable serializable = (ISerializable)TypeInitializer.CreateInstance(type);
				obj.Value = serializable;
				serializable.Deserialize(this);
				this.path.RemoveLast();

				while (this.xmlReader.Depth > depth)
				{
					if (this.xmlReader.Read() == false)
						break;
				}

				return obj.Value;
			}

			Func<string, object> parser = Parser.GetParseHandler(type);
			if (parser != null)
			{
				string s = this.xmlReader.ReadElementContentAsString();
				obj.Value = parser(s);
				this.path.RemoveLast();
				return obj.Value;
			}
			
			if (type.IsArray == true)
			{
				List<object> elements = new List<object>();
				Type elementType = type.GetElementType();
				if (elementType == null)
					elementType = typeof(Object);

				this.xmlReader.Read();
				while (this.xmlReader.NodeType != XmlNodeType.EndElement)
				{
					if (this.xmlReader.NodeType == XmlNodeType.Element && this.xmlReader.LocalName == Serializer.NameElement)
					{
						elements.Add(this.Deserialize(elementType));
					}
					this.xmlReader.Read();
				}

				Array array = Array.CreateInstance(elementType, elements.Count);
				for (int i = 0; i < elements.Count; i++)
					array.SetValue(elements[i], i);
				obj.Value = array;

				this.xmlReader.MoveToNode(XmlNodeType.EndElement, false);
				this.xmlReader.Read();
				this.path.RemoveLast();
				return obj.Value;
			}


			obj.Value = TypeInitializer.CreateInstance(type);
			IEnumerable<FieldInfo> fis = type.GetRuntimeFields();
			this.xmlReader.Read();
			while (this.xmlReader.NodeType != XmlNodeType.EndElement)
			{
				if (this.xmlReader.NodeType == XmlNodeType.Element)
				{
					foreach (FieldInfo fi in fis)
					{
						if (fi.IsStatic)
							continue;
						string fieldName;
						if (fi.Name.EndsWith(Serializer.PropertyBackingField) == true)
							fieldName = XmlConvert.DecodeName(fi.Name.Substring(1, fi.Name.Length - Serializer.PropertyBackingField.Length - 1));
						else
							fieldName = XmlConvert.DecodeName(fi.Name);

						if (fieldName == this.xmlReader.LocalName)
						{
							object val = this.Deserialize(fi.FieldType);
							fi.SetValue(obj.Value, val);
							break;
						}
					}
				}
				this.xmlReader.Read();
			}

			this.xmlReader.MoveToNode(XmlNodeType.EndElement, false);
			this.xmlReader.Read();
			this.path.RemoveLast();
			return obj.Value;
		}

		private object ReadAsReference()
		{
			string referencePath = this.xmlReader.GetAttribute(Serializer.NameReference);
			if (referencePath == null)
				return null;

			string[] parts = referencePath.Split('.');
			return this.path.List2[parts.Length - 1].Value;
		}

		public void Close()
		{
			this.xmlReader.Dispose();
		}

		void IDisposable.Dispose()
		{
			this.Close();
		}
	}
}
